/*
 * Copyright (c) 2018-2999 九五云信息科技有限公司 All rights reserved.
 *
 * https://www.gz-yami.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.platform.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yami.shop.bean.enums.ProdStatusEnums;
import com.yami.shop.bean.model.Category;
import com.yami.shop.bean.model.Supplier;
import com.yami.shop.bean.model.User;
import com.yami.shop.bean.param.*;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.common.util.PoiExcelUtil;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.coupon.comment.model.CouponUser;
import com.yami.shop.coupon.comment.service.CouponService;
import com.yami.shop.coupon.comment.service.CouponUserService;
import com.yami.shop.platform.task.AnalysisSalseTask;
import com.yami.shop.security.comment.constants.SecurityConstants;
import com.yami.shop.security.comment.enums.App;
import com.yami.shop.security.comment.model.AppConnect;
import com.yami.shop.security.comment.service.AppConnectService;
import com.yami.shop.service.UserService;
import com.yami.shop.user.comment.service.UserTagService;
import com.yami.shop.user.comment.service.UserTagUserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * @author lgh on 2018/10/16.
 */
@RestController
@RequestMapping("/admin/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private AppConnectService appConnectService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private UserTagUserService userTagUserService;

    @Autowired
    private CouponUserService couponUserService;

    /**
     * 分页获取
     */
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('plateform:user:page')")
    public ResponseEntity<IPage<User>> page(User user,PageParam<User> page) {
//        IPage<User> userPage = userService.page(page, new LambdaQueryWrapper<User>()
//                .like(StrUtil.isNotBlank(user.getNickName()), User::getNickName, user.getNickName())
//                .eq(user.getStatus() != null, User::getStatus, user.getStatus())
//                .isNotNull(User::getNickName)
//                .orderByDesc(User::getUserRegtime));
        IPage<User> userPage =  userService.getUserPage(page,user);
//        for (User userResult : userPage.getRecords()) {
//            userResult.setNickName(userResult.getNickName());
//        }
        return ResponseEntity.ok(userPage);
    }

    /**
     * 获取信息
     */
    @GetMapping("/info/{userId}")
    @PreAuthorize("@pms.hasPermission('plateform:user:info')")
    public ResponseEntity<UserManagerParam> info(@PathVariable("userId") String userId) {
        UserManagerParam param = userService.getuserInfo(userId);;
        List<UserTagParam> userTagParams = userTagUserService.getUserTagsUserByUserId(userId);
        param.setUserTagParam(userTagParams);
        CouponUserParam couponUser = couponUserService.getCouponCountByUserId(userId);
        param.setCouponUserParam(couponUser);
        return ResponseEntity.ok(param);
    }

    /**
     * 修改
     */
    @PutMapping
    @PreAuthorize("@pms.hasPermission('plateform:user:update')")
    public ResponseEntity<Void> update(@RequestBody User user) {
        user.setModifyTime(new Date());
        user.setNickName(user.getNickName());
        userService.updateById(user);
        List<Integer> appTypes = App.listAppType();
        for (Integer appType : appTypes) {
            Collection<OAuth2AccessToken> tokensByClientIdAndUserName = tokenStore.findTokensByClientIdAndUserName(String.valueOf(appType),user.getUserId());
            Map<String, String> values = new LinkedHashMap<>();
            values.put("username", user.getUserId());
            values.put("client_id", String.valueOf(appType));
            values.put("scope", "");
            String key = SecurityConstants.YAMI_OAUTH_PREFIX + "auth_to_access:" + generateKey(values);
            RedisUtil.del(key);
            for (OAuth2AccessToken oAuth2AccessToken : tokensByClientIdAndUserName) {
                RedisUtil.del(SecurityConstants.YAMI_OAUTH_PREFIX + "access:" + oAuth2AccessToken.getValue());
            }
        }
        return ResponseEntity.ok().build();
    }
    /**
     * 分页获取
     */
    @GetMapping("/userPage")
    public ResponseEntity<IPage<UserManagerParam>> userPage(PageParam<User> page, UserManagerReqParam user) {
        IPage<UserManagerParam> userPage =  userService.getUserInfoPage(page,user);
        return ResponseEntity.ok(userPage);
    }

    /**
     * 导出用户信息
     */
    @GetMapping("/exportUser")
    public void exportUser(UserManagerReqParam user, HttpServletResponse response){
        // 开始时间
        long start = System.currentTimeMillis();
        PageParam<User> userPageParam = new PageParam<>();
        userPageParam.setCurrent(1);
        userPageParam.setSize(10L);
        IPage<UserManagerParam> userPage =  userService.getUserInfoPage(userPageParam,user);
        // 总共有多少条数据
        Long total = userPage.getTotal();
        // 用户有很多，考虑2000条以上数据的导出 一个最多104w 行数据
        Long rowMaxCount = 500000L;
        // 每一次查询条数
        Long eachCount = 1000L;
        // 这里不用PageParam<User> ，为了方便自由调整查询条数
        Page<User> pages = new Page<>();
        pages.setCurrent(1);

        List<UserManagerParam> list = new ArrayList<>();
        // 查询记录数
        //通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getBigWriter();
        //
        String fileName = I18nMessage.getMessage("yami.excel.user.info") + ".xls";
        if (total<=rowMaxCount){
            if (total<= eachCount) {
                pages.setSize(total);
                userPage = userService.getUserInfoPage(pages, user);
                exportExcel(userPage.getRecords(), 1, 1, response,writer);
                // 导出
                PoiExcelUtil.writeExcel(fileName,writer,response);
            } else {
                // 开启多线程查询，每eachCount(100)行数据为一个线程开始
                int pageSize = getPageSize(total, eachCount);
//                ExecutorService execservice = new ThreadPoolExecutor(4,10,200L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(10));
                ExecutorService execservice =Executors.newFixedThreadPool(15);
                try {
                    List<Callable<List<UserManagerParam>>> tasks = new ArrayList<Callable<List<UserManagerParam>>>();
                    for (int i = 1; i <= pageSize; i++) {
                        Page<User> pagesIndex = new Page<>();
                        pagesIndex.setCurrent(i);
                        pagesIndex.setSize(eachCount);
                        Callable<List<UserManagerParam>> task = new AnalysisSalseTask(userService,user,pagesIndex);
                        tasks.add(task);
                    }
                    List<Future<List<UserManagerParam>>> futures = execservice.invokeAll(tasks);
                    if (futures != null && futures.size() > 0){
                        for (Future<List<UserManagerParam>> future : futures){
                            list.addAll(future.get());
                        }
                    }
                    execservice.shutdown();
                    long end = System.currentTimeMillis();
                    System.out.println("线程查询数据用时:"+(end-start)+"ms");
                } catch (Exception e) {
                    System.out.println("多线程查询异常");
                }
                userPage.setRecords(list);
                exportExcel(list, 1, 1, response,writer);
                // 导出
                PoiExcelUtil.writeExcel(fileName,writer,response);
                list.clear();
            }
        } else {
            // 分多少个 sheet
            int pageSize = getPageSize(total,rowMaxCount);
            eachCount = 1000L;
            List<String> filepaths = new ArrayList<>();
            for (int i = 1; i <= pageSize; i++) {
                list.clear();
                int size = getPageSize(rowMaxCount, eachCount);
                try {
                    ExecutorService execservice = new ThreadPoolExecutor(4, 10, 200L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(10));
                    List<Callable<List<UserManagerParam>>> tasks = new ArrayList<Callable<List<UserManagerParam>>>();
                    for (int j = 1; j <= size; j++) {
                        Page<User> pagesIndex = new Page<>();
                        pagesIndex.setSize(eachCount);
                        pagesIndex.setCurrent((i-1) * size + j);
                        Callable<List<UserManagerParam>> task = new AnalysisSalseTask(userService, user, pagesIndex);
                        tasks.add(task);
                    }
                    List<Future<List<UserManagerParam>>> futures = execservice.invokeAll(tasks);
                    if (Objects.nonNull(futures) && futures.size() > 0) {
                        for (Future<List<UserManagerParam>> future : futures) {
                            list.addAll(future.get());
                        }
                    }
                    tasks.clear();
                    execservice.shutdown();
                } catch (Exception e) {
                    System.out.println("多线程查询异常");
                }
                // 序号 (i-1) * rowMaxCount + 1
                int rowStart = new BigDecimal(i - 1).multiply(new BigDecimal(rowMaxCount)).add(new BigDecimal(1)).intValue();
                //方法1： 导出到一个临时文件，
                // 然后合并小于50W行的Excel到100W行为一个Excel文件,此处逃过，直接是100W行为一个文件
                // 然后将100W的每一个Excel文件进行压缩

                // 方法2：分多个sheet 导出
                // 此时达到 rowMaxCount 行数据 list 导出到excel
                exportExcel(list, rowStart, i-1, response, writer);
                list.clear();
            }
            // 导出
            PoiExcelUtil.writeExcel(fileName,writer,response);
        }


    }

    private int getPageSize(Long total, Long eachCount) {
        int pageSize = new BigDecimal(total).divide(new BigDecimal(eachCount),1).intValue();
        int mod = new BigDecimal(total).divideAndRemainder(new BigDecimal(eachCount))[1].intValue();
        if (mod > 0) {
            pageSize = pageSize + 1;
//            pageSize = new BigDecimal(pageSize).add(new BigDecimal(1)).intValue();
        }
        return pageSize;
    }

    private String generateKey(Map<String, String> values) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
            byte[] bytes = digest.digest(values.toString().getBytes(StandardCharsets.UTF_8));
            return String.format("%032x", new BigInteger(1, bytes));
        } catch (NoSuchAlgorithmException nsae) {
            throw new IllegalStateException("MD5 algorithm not available.  Fatal (should be in the JDK).", nsae);
        }
    }

    /**
     * @param list 导出的数据
     * @param rowStart 开始的行数
     * @param pages 一共有多少页
     */
    private void exportExcel(List<UserManagerParam> list,int rowStart,int pages, HttpServletResponse response,ExcelWriter writer) {

        // 商品导出or模板
        List<String> headerList;
        String[] header = {
                I18nMessage.getMessage("yami.excel.user.number"),           //"序号",
                I18nMessage.getMessage("yami.excel.user.userNickname"),     //"用户昵称",
                I18nMessage.getMessage("yami.excel.user.userName"),         //"用户名称",
                I18nMessage.getMessage("yami.excel.user.contactNumber"),    //"联系电话",
                I18nMessage.getMessage("yami.excel.user.memberLevel"),      //"会员等级",
                I18nMessage.getMessage("yami.excel.user.memberType"),       //"会员类型",
                I18nMessage.getMessage("yami.excel.user.accScore"),         //"用户积分",
                I18nMessage.getMessage("yami.excel.user.status"),           //"状态",
                I18nMessage.getMessage("yami.excel.user.amountConsum"),     //"消费金额",
                I18nMessage.getMessage("yami.excel.user.amountActPaid"),    //"实付金额",
                I18nMessage.getMessage("yami.excel.user.numberConsum"),     //"消费次数",
                I18nMessage.getMessage("yami.excel.user.aveDiscount"),      //"平均折扣",
                I18nMessage.getMessage("yami.excel.user.rechargeAmount"),   //"充值金额",
                I18nMessage.getMessage("yami.excel.user.topUpTimes"),       //"充值次数",
                I18nMessage.getMessage("yami.excel.user.refundAmount"),     //"退款金额",
                I18nMessage.getMessage("yami.excel.user.numberOfRefund"),   //"退款次数",
                I18nMessage.getMessage("yami.excel.user.accPoints"),        //"累计积分",
                I18nMessage.getMessage("yami.excel.user.currBalance"),      //"当前余额",
                I18nMessage.getMessage("yami.excel.user.accBalance"),       //"累计余额",
                I18nMessage.getMessage("yami.excel.user.regTime"),          //"注册时间",
                I18nMessage.getMessage("yami.excel.user.receConsumDate")    //"最近消费时间"
        };
        headerList = Arrays.asList(header);
        Sheet sheet = writer.getSheet();
        writer.merge(headerList.size() - 1, I18nMessage.getMessage("yami.excel.user.info"));
        writer.writeRow(headerList);
        for (int i = 0; i < headerList.size(); i++) {
            if (i==19 || i==20) {
                sheet.setColumnWidth(i, 30 * 256);
            } else {
                sheet.setColumnWidth(i, 20 * 256);
            }
        }
        // 如果要导出的数据为空，导出一个模板
        if (CollectionUtils.isEmpty(list)) {
            PoiExcelUtil.writeExcel(response, writer);
            return;
        }
        int row = rowStart;
        int size = list.size();
        for (UserManagerParam param : list) {
            int firstRow = row + 1;
            int lastRow = row + 1;
            int col = -1;
            // 序号
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,rowStart++);
            // 用户昵称
            String nickName = Objects.isNull(param.getNickName())?"":param.getNickName();
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,nickName);
            // 用户名称
            String realName = Objects.isNull(param.getRealName())?"":param.getRealName();
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,realName);
            // 联系电话
            String userMobile = Objects.isNull(param.getUserMobile())?"":param.getUserMobile();
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,userMobile);
            // 会员等级
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getLevelName());
            // 会员类型
            String levelType = param.getLevelType() == 0 ? "普通会员": "付费会员";
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,levelType);
            // 用户积分
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getScore());
            // 状态
            String status = param.getStatus() == 0 ? "禁用": "正常";
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,status);
            // 消费金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getConsAmount());
            // 实付金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getActualAmount());
            // 消费次数
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getConsTimes());
            // 平均折扣
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getAverDiscount());

            // 充值金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getRechargeAmount());
            // 充值次数
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getRechargeTimes());

            // 退款金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getAfterSaleAmount());
            // 退款次数
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getAfterSaleTimes());
            // 当前积分
//            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getCurrentScore());
            // 累计积分
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getSumScore());
            // 当前余额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getCurrentBalance());
            // 累计余额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,param.getSumBalance());
            // 注册时间
            String regTime = "";
            if (Objects.nonNull(param.getUserRegtime())){
                regTime = DateUtil.format(param.getUserRegtime(),"yyyy-MM-dd HH:mm:ss");
            }
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,regTime);
            // 最近消费时间
            String recTime = "";
            if (Objects.nonNull(param.getUserRegtime())){
                recTime = DateUtil.format(param.getReConsTime(),"yyyy-MM-dd HH:mm:ss");
            }
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,recTime);
            row++;
        }
    }


}
